# -*- coding: utf-8 -*-
#
# Copyright (c) 2012, Rafael Pivato
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Rafael Pivato nor the names of its contributors
#       may be used to endorse or promote products derived from this
#       software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL RAFAEL PIVATO BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import difflib
import urllib
import webapp2

from google.appengine.ext import db
from wowgae.decorators import membership_required
from wowgae.web import WebHandler
from wowgae.model import WikiPage
from wowgae.model import Account
from wowgae.model import WikiPageRevision
from wowgae import human_delta_now
from wowgae.macro import GeneralInlineMacro
from wowgae.macro import GeneralSectionMacro
from wowgae.macro import InstructionSectionMacro
from wowgae.macro import ParagraphSectionMacro
from wowgae.macro import CgiEscapeInlineMacro


class LexemeType(object):
    """Helps tagging different types of tokens."""

    TEXT_SEQUENCE = 1


class Scanner(object):
    """Helps scanning or reading a Wiki content."""

    def __init__(self, content):
        """Creates a new Scanner able to read the given content."""
        self.content = content
        self.len = len(content)
        self.pos = 0

    def sof(self):
        """Tells if we are at start of the content."""
        return self.pos == 0

    def eof(self):
        """Tells if we passed the end of content."""
        return self.pos >= self.len

    def skip(self, count=1):
        """Advances current position by 'count' characters."""
        limit = min(self.pos + count, self.len)
        self.pos = limit

    def read(self, count=1):
        """Reads one character from current position."""
        limit = min(self.pos + count, self.len)
        chars = self.content[self.pos:limit]
        self.pos = self.pos + len(chars)
        return chars

    def peek(self, count=1):
        """Peeks the given number of characters from the content."""
        limit = min(self.pos + count, self.len)
        chars = self.content[self.pos:limit]
        return chars


class Writer(object):
    """Helps writting content to an output buffer."""

    def __init__(self):
        """Initializes an empty buffer."""
        self.content_list = []

    def write(self, b):
        """Writes the given buffer to the end of our content."""
        self.content_list.append(b)

    def get_content(self):
        """Returns the content as a string."""
        return u''.join(self.content_list)


class Parser(object):
    """Parses a Wiki input and produces HTML output."""

    def __init__(self, content):
        self.writer = Writer()
        self.scanner = Scanner(content)
        self.inline_macros = GeneralInlineMacro(self)
        self.section_macros = GeneralSectionMacro(self)
        self.default_section = ParagraphSectionMacro(self)
        self.default_inline = CgiEscapeInlineMacro(self)

    def process_instructions(self):
        """Processes all instructions at the beggining of the content."""
        macro = InstructionSectionMacro(self)
        while macro.match(None, None):
            macro.process(None, None)

    def process_sections(self):
        """Processes section macros."""
        while not self.scanner.eof():
            if self.section_macros.match(None, None):
                self.section_macros.process(None, None)
            else:
                self.default_section.process(None, None)

    def process_inlines(self):
        """Processes inline macros."""
        while not self.scanner.eof() and self.scanner.peek() != '\n':
            if self.inline_macros.match(None):
                self.inline_macros.process(None)
            else:
                self.default_inline.process(None)

    def parse(self, content):
        """Parses the Wiki text into HTML."""
        self.process_instructions()
        self.process_sections()
        self.section_macros.process_eod()
        return self.writer.get_content()


def wiki_to_html(content):
    parser = Parser(content)
    return parser.parse(content)


class WikiHandler(WebHandler):
    """Handles Wiki Pages requests."""

    def __history_data(self, page_data):
        data = []
        page_name = page_data['name'].lower()
        data.append({
            'revision': page_data['revision'],
            'author': page_data['author'],
            'view_role': page_data['view_role'],
            'edit_role': page_data['edit_role'],
            'delta': page_data['modified'],
            'link': webapp2.uri_for('wiki', self.request, page=page_name),
        })
        previous_id = page_data['revision']
        revisions = WikiPageRevision.load_history(page_name)
        for revision in revisions:
            revision_id = revision.key().id()
            data[-1]['diff'] = webapp2.uri_for('wiki-diff', self.request,
                                               page=page_name,
                                               reva=revision_id,
                                               revb=previous_id)
            delta_value, delta_label = human_delta_now(revision.modified)
            delta = {'value': delta_value, 'label': delta_label}
            data.append({
                'revision': revision_id,
                'author': revision.author,
                'view_role': revision.view_role,
                'edit_role': revision.edit_role,
                'delta': delta,
                'link': webapp2.uri_for('wiki-revision', self.request,
                                        page=page_name, revision=revision_id),
            })
            previous_id = revision_id
        data[-1]['diff'] = data[-1]['link']
        return data

    def __page_data(self, page):
        """Loads Wiki Page data into a dictionary."""
        if not isinstance(page, WikiPage):
            page = WikiPage.load_page(page)
        content = page.content
        directives = page.directives()
        modified_value, modified_label = human_delta_now(page.modified)
        modified = {'value': modified_value, 'label': modified_label}
        data = {'name': page.name(), 'content': content, 'author': page.author,
                'revision': page.revision, 'title': page.title,
                'modified': modified, 'directives': directives,
                'view_role': page.view_role, 'edit_role': page.edit_role}
        return data

    def __revision_data(self, page, revision):
        """Loads Wiki Page revision data into a dictionary."""
        page_key = None
        if isinstance(page, WikiPage):
            page_key = page.key()
        else:
            page_key = db.Key.from_path('WikiPage', page)
        model = WikiPageRevision.get_by_id(revision, page_key)
        if model is None:
            self.abort(404)
        content = model.content
        parsed = wiki_to_html(content)
        data = {'name': page_key.name(), 'content': content,
                'revision': revision, 'parsed': parsed}
        return data

    def __links_data(self, page):
        """Returns links data to be used in a Wiki HTML page."""
        if isinstance(page, WikiPage):
            key_name = page.key().name()
            page_name = key_name
        else:
            key_name = WikiPage.key_from_name(page)
            page_name = page
        request = self.request
        edit = webapp2.uri_for('wiki-edit', request, page=key_name)
        create = webapp2.uri_for('wiki-edit', request, page=page_name)
        history = webapp2.uri_for('wiki-history', request, page=key_name)
        view = webapp2.uri_for('wiki', request, page=key_name)
        data = {'edit': edit, 'history': history, 'view': view,
                'create': create}
        return data

    def get(self, page):
        """Gets the content of a Wiki Page or a blank page."""
        if isinstance(page, str):
            page = page.decode('utf-8')
            page = urllib.unquote(page)
        page_data = self.__page_data(page)
        if 'REDIRECT' in page_data:
            if self.request.get('redirect') != 'no':
                target_page = page_data['directives']['REDIRECT']
                redir_uri = webapp2.uri_for('wiki', page=target_page)
                self.redirect(redir_uri)
                return

        # Check permissions to view this page
        self.login.enforce_account_role(page_data['view_role'],
                                        allow_public=True)

        links_data = self.__links_data(page)
        new_url = webapp2.uri_for('wiki', page=page_data['name'])
        self.replace_browser_url(new_url)
        data = {'page': page_data, 'links': links_data, 'mode': 'view'}
        template = self.template
        if page_data['revision'] == 0:
            template.use('wiki', 'empty')
        else:
            template.use('wiki', 'view')
            parser = Parser(page_data['content'])
            parsed = parser.parse(page_data['content'])
            page_data['parsed'] = parsed
        template.update(wiki=data)
        template.write()

    def get_home(self):
        """Redirects to MainPage."""
        uri = webapp2.uri_for('wiki', self.request, page='Home')
        self.redirect(uri, permanent=True)

    def get_edit(self, page):
        """Gets the content of a Wiki Page for editing or creation."""
        if isinstance(page, str):
            page = page.decode('utf-8')
            page = urllib.unquote(page)
        page_data = self.__page_data(page)

        # Check permissions to edit this page
        self.login.enforce_account_role(page_data['edit_role'])

        links_data = self.__links_data(page)
        new_url = webapp2.uri_for('wiki-edit', page=page_data['name'])
        self.replace_browser_url(new_url)
        data = {'page': page_data, 'links': links_data, 'mode': 'edit'}
        template = self.template
        template.use('wiki', 'edit')
        template.update(wiki=data)
        template.write()

    def get_history(self, page):
        """Gets the history for an existing Wiki Page."""
        if isinstance(page, str):
            page = page.decode('utf-8')
            page = urllib.unquote(page)
        page_data = self.__page_data(page)
        links_data = self.__links_data(page)
        history_data = self.__history_data(page_data)

        # Check permissions to view this page history
        self.login.enforce_account_role(page_data['view_role'])

        new_url = webapp2.uri_for('wiki-history', page=page_data['name'])
        self.replace_browser_url(new_url)
        data = {'page': page_data, 'links': links_data,
                'history': history_data, 'mode': 'history'}
        template = self.template
        template.use('wiki', 'history')
        template.update(wiki=data)
        template.write()

    def get_revision(self, page, revision):
        """Gets the revision for an existing Wiki Page."""
        if isinstance(page, str):
            page = page.decode('utf-8')
            page = urllib.unquote(page)
        revision = int(revision)
        page_data = self.__page_data(page)
        revision_data = self.__revision_data(page, revision)

        # Check permissions to view this page revision
        self.login.enforce_account_role(page_data['view_role'])

        links_data = self.__links_data(page)
        data = {'page': revision_data, 'links': links_data, 'mode': 'revision'}
        template = self.template
        template.use('wiki', 'revision')
        template.update(wiki=data)
        template.write()

    def get_diff(self, page, reva, revb):
        """Gets the differents from one revision to another."""
        if isinstance(page, str):
            page = page.decode('utf-8')
            page = urllib.unquote(page)
        reva = int(reva)
        revb = int(revb)
        if reva == revb:
            self.abort(404)
        page_data = self.__page_data(page)

        # Check permissions to view this page history
        self.login.enforce_account_role(page_data['view_role'])

        if reva == page_data['revision']:
            reva = page_data
            if 'parsed' not in page_data:
                page_data['parsed'] = wiki_to_html(page_data['content'])
        else:
            reva = self.__revision_data(page, reva)
        if revb == page_data['revision']:
            revb = page_data
            if 'parsed' not in page_data:
                page_data['parsed'] = wiki_to_html(page_data['content'])
        else:
            revb = self.__revision_data(page, revb)
        differ = difflib.HtmlDiff(wrapcolumn=30)
        reva_iso = reva['content'].encode('iso-8859-1', 'replace')
        reva_iso = reva_iso.splitlines()
        revb_iso = revb['content'].encode('iso-8859-1', 'replace')
        revb_iso = revb_iso.splitlines()
        diff = differ.make_table(reva_iso, revb_iso)
        diff = diff.decode('iso-8859-1', 'replace')
        links_data = self.__links_data(page)
        data = {'page': page_data, 'links': links_data, 'mode': 'diff',
                'reva': reva, 'revb': revb, 'diff': diff}
        template = self.template
        template.use('wiki', 'diff')
        template.update(wiki=data)
        template.write()

    @membership_required
    def post(self, page):
        """Post the content for a Wiki Page."""
        if isinstance(page, str):
            page = page.decode('utf-8')
            page = urllib.unquote(page)

        page_data = self.__page_data(page)

        # Check permissions to edit this page revision
        self.login.enforce_account_role(page_data['edit_role'])

        # Get the Last Modified mark
        revision = self.request.get('revision')

        # Author is current user editing this page
        author = self.current_account.name

        # Check the client provided valid roles
        view_role = self.request.get('view_role')
        edit_role = self.request.get('edit_role')
        if not Account.is_valid_role(view_role):
            view_role = self.current_account.role
        if not Account.is_valid_role(edit_role):
            edit_role = self.current_account.role

        # No role access can be higher than account role
        if Account.cmp_roles(view_role, self.current_account.role) > 0:
            view_role = self.current_account.role
        if Account.cmp_roles(edit_role, self.current_account.role) > 0:
            edit_role = self.current_account.role

        # Edit role can not be public
        if edit_role == 'public':
            edit_role = 'member'

        # View role can not be higher than edit role
        if Account.cmp_roles(view_role, edit_role) > 0:
            view_role = edit_role

        # Get the content and submit the edit action
        content = self.request.get('content')
        page = WikiPage.edit_page(page, revision, author, view_role, edit_role,
                                  content)

        self.get(page)
